package org.chorus;

import jwo.landserf.process.io.LandSerfIO;
import jwo.landserf.structure.Footprint;
import jwo.landserf.structure.RasterMap;
import org.gicentre.utils.move.ZoomPan;
import processing.core.PApplet;
import processing.core.PConstants;
import processing.core.PImage;
import processing.core.PVector;

//  **********************************************************************************
/** Sketch for rotating surfaces so that scan edges are approximately aligned with 
 *  the rectangular raster edges.
 *  @author Jo Wood
 *  @version 1.0, 4th June, 2011
 */
//  **********************************************************************************

/* This file is part of the Chorus river bed analysis software package. Chorus is free software:
 * you can redistribute it and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 * 
 * Chorus is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this
 * source code (see COPYING included with this source code). If not, see 
 * http://www.gnu.org/licenses/.
 */

@SuppressWarnings("serial")
public class Rotator extends PApplet
{
	// ----------------------------- Object variables ------------------------------

	private String baseDir = "/Users/jwo/data/riverBed/";	// TODO: Use properties file to store folder locations.

	private float globalRotation, localRotation;                 // Angle about which to rotate points in radians.
	private PVector pressPos,dragPos;       // Clicked and dragged mouse positions.
	private PImage image;                   // Off screen buffer.
	private ZoomPan zoomer;
	private boolean isShiftDown;
	private RasterMap origRaster;

	// ------------------------------ Starter method -------------------------------

	/** Starts the sketch as an application.
	 *  @param args Command line arguments (ignored). 
	 */
	public static void main(String[] args)
	{   
		PApplet.main(new String[] {"org.chorus.Rotator"});
	}

	// ---------------------------------- Methods ----------------------------------

	/** Initialises the rotator.
	 */
	public void setup()
	{
		size(1000,1000);
		smooth();

		if (frame !=null)
		{
			frame.setTitle("Surface Rotator");
		}

		zoomer = new ZoomPan(this);
		zoomer.setMouseMask(SHIFT);
		isShiftDown = false;
		globalRotation = 0;
		localRotation = 0;
	
		// Load DEM to be rotated.
		origRaster = LandSerfIO.readRaster(baseDir+"photogrammetryTestBed/surface3Phototray0.5mmDetrended.srf");
		//origRaster = LandSerfIO.readRaster(baseDir+"sample11_5deg_100nm.srf");
		//origRaster = LandSerfIO.readRaster(baseDir+"afterCrayfishNoVoids.srf");
		//origRaster = LandSerfIO.readRaster(baseDir+"delme.srf");
		
		//drawToImage();
		image = DisplayUtils.createImage(this, origRaster, RasterMap.ELEV);
	}

	/** Draws the rotator application so surface can be rotated graphically.
	 */
	public void draw()
	{
		background(255);
		noLoop();
		imageMode(PConstants.CENTER);

		zoomer.transform();

		stroke(0,200);
		strokeWeight(2);
		noFill();

		pushMatrix();
		translate(width/2,height/2);
		rotate(globalRotation+localRotation);
		translate(-width/2,-height/2);
		
		// Draw the raster at its current rotation.
		image(image,width/2,height/2);		

		popMatrix();

		// Draw grid
		strokeWeight(0.2f);
		for (float y=0; y< height; y+= height/20f)
		{
			line(0,y,width,y);
		}

		for (float x=0; x< width; x+= width/20f)
		{
			line(x,0,x,height);
		}
		
		strokeWeight(1);
		line(0,height/2,width,height/2);
		line(width/2,0,width/2,height);
	}

	/** Stores the position of the mouse when first pressed.
	 */
	public void mousePressed()
	{
		if (isShiftDown == false)
		{
			pressPos = new PVector(zoomer.getMouseCoord().x,zoomer.getMouseCoord().y);
			pressPos.sub(width/2, height/2, 0);
		}
		loop();
	}

	/** Stops any rotation once the mouse is released.
	 */
	public void mouseReleased()
	{
		if (isShiftDown == false)
		{
			pressPos = null;
			dragPos = null;
			globalRotation = (globalRotation+localRotation)%(float)(2*Math.PI);
			localRotation = 0;
		}
		loop();
	}

	/** Identifies the rotation angle based on how the mouse has been dragged.
	 */
	public void mouseDragged()
	{
		if (isShiftDown == false)
		{
			dragPos = new PVector(zoomer.getMouseCoord().x,zoomer.getMouseCoord().y);
			dragPos.sub(width/2, height/2, 0);

			// Cannot use PVector.anglebetween() since we need to preserve a full 360 degrees of rotation.
			localRotation = atan2(dragPos.y,dragPos.x) - atan2(pressPos.y,pressPos.x);
		}

		loop();
	}

	/** Responds to key presses such as rotating rater, activating zooming with the shift key etc.
	 */
	public void keyPressed()
	{
		if (key == 'r' || (key == 'R'))
		{
			zoomer.reset();
			loop();
		}
		else if (key == 's' || (key == 'S'))
		{
			saveRotatedRaster();
			loop();
		}


		if ((key==CODED) && (keyCode==SHIFT))
		{
			isShiftDown = true;
		}
	}

	/** Responds to the shift key being released by deactivating zooming an panning.
	 */
	public void keyReleased()
	{
		if ((key==CODED) && (keyCode==SHIFT))
		{
			isShiftDown = false;
		}
	}

	// ------------------------------ Private methods ------------------------------

	/** Creates a new raster at current rotation angle and saves it.
	 */
	private void saveRotatedRaster()
	{
		// Find dimensions of new raster by rotating corners of original
		double cx = origRaster.getNumCols()/2.0;
		double cy = origRaster.getNumRows()/2.0;
		double cosAlpha = Math.cos(globalRotation + localRotation);
		double sinAlpha = Math.sin(globalRotation + localRotation);
		
		//Footprint bounds = origRaster.getBounds();
				
		// Bottom left.
		PVector blPrime = new PVector((float)(cx-cx*cosAlpha-cy*sinAlpha),
									  (float)(cy+cx*sinAlpha-cy*cosAlpha));
		// Top left.
		PVector tlPrime = new PVector((float)(cx-cx*cosAlpha-cy*sinAlpha + sinAlpha*origRaster.getNumRows()),
									  (float)(cy+cx*sinAlpha-cy*cosAlpha + cosAlpha*origRaster.getNumRows()));
		// Bottom right
		PVector brPrime = new PVector((float)(cx-cx*cosAlpha-cy*sinAlpha + cosAlpha*origRaster.getNumCols()),
									  (float)(cy+cx*sinAlpha-cy*cosAlpha - sinAlpha*origRaster.getNumCols()));
		// Top right
		PVector trPrime = new PVector((float)(cx-cx*cosAlpha-cy*sinAlpha + cosAlpha*origRaster.getNumCols() + sinAlpha*origRaster.getNumRows()),
				  					  (float)(cy+cx*sinAlpha-cy*cosAlpha - sinAlpha*origRaster.getNumCols() + cosAlpha*origRaster.getNumRows()));
		
		float minX = Math.min(Math.min(blPrime.x, tlPrime.x),Math.min(brPrime.x, trPrime.x));
		float maxX = Math.max(Math.max(blPrime.x, tlPrime.x),Math.max(brPrime.x, trPrime.x));
		float minY = Math.min(Math.min(blPrime.y, tlPrime.y),Math.min(brPrime.y, trPrime.y));
		float maxY = Math.max(Math.max(blPrime.y, tlPrime.y),Math.max(brPrime.y, trPrime.y));
		
		int numRows = Math.round(maxY-minY);
		int numCols = Math.round(maxX-minX);
				
		RasterMap rotatedRaster = new RasterMap(origRaster);
		rotatedRaster.setBounds(new Footprint(minX,minY,maxX-minX,maxY-minY));
		rotatedRaster.reset(numRows, numCols, origRaster.getXRes(), origRaster.getYRes());
		
		// Find new x and y range based on rotation.
		for (int row=0; row<rotatedRaster.getNumRows(); row++)
		{
			for (int col=0; col<rotatedRaster.getNumCols(); col++)
			{
				PVector pPrime = new PVector((float)(cx-cx*cosAlpha-cy*sinAlpha + cosAlpha*col + sinAlpha*row),
											 (float)(cy+cx*sinAlpha-cy*cosAlpha - sinAlpha*col + cosAlpha*row));
				Footprint rotatedCell = rotatedRaster.getFootprint(Math.round(pPrime.y), Math.round(pPrime.x));
				if (rotatedCell != null)
				{
					rotatedRaster.setAttribute(row, col, origRaster.getAttribute(rotatedCell));
				}
			}
		}
	
		LandSerfIO.write(rotatedRaster, baseDir+"rotated.srf");
		
		
		System.err.println("BL: "+blPrime+" BR "+brPrime+" TL: "+tlPrime+" TR: "+trPrime);
		
		System.err.println("Num Rows: "+rotatedRaster.getNumRows()+" Cols: "+rotatedRaster.getNumCols());

		
	}
}
